package com.gopay.scheduler.job;


import com.gopay.common.constants.fund.FundSettleStatusEnum;
import com.gopay.common.constants.txncd.IntTxnCd;
import com.gopay.common.domain.cps.transfer.Tran02014Detail;
import com.gopay.common.domain.cps.transfer.Tran02014VO;
import com.gopay.common.domain.cps.transfer.Tran02015Detail;
import com.gopay.common.domain.cps.transfer.Tran02015VO;
import com.gopay.common.domain.cps.vo.CPSResponse;
import com.gopay.common.domain.fund.FundGopayCpsGenOrder;
import com.gopay.common.exception.GopayException;
import com.gopay.common.util.DateUtils;
import com.gopay.common.util.FastJsonUtil;
import com.gopay.common.util.JsonUtils;
import com.gopay.cron.web.util.CallCPSUtil;
import com.gopay.scheduler.GopayBaseJob;
import com.gopay.scheduler.service.fund.FundBaseOperateService;
import com.gopay.sms.client.GopaySMSHttpClient;
import com.gopay.sms.common.constants.SMSAppId;
import com.gopay.sms.common.domain.RequestVO;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;

import java.io.IOException;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.concurrent.*;
/*
 * Copyright (c) 2016. www.gopay.com.cn Inc.All rights reserved.
 * 文件名称 : Fund2GopaySettleJob
 * 创建日期 : 17/4/19 10:58
 * Author  : baiyunpeng
 * 此任务做了两个事:1,更新内转相关连的02018订单为成功,2,将付款未完成的内转订单二次付款
 */
public class Fund2GopaySettleJob extends GopayBaseJob {
    private Logger logger = LoggerFactory.getLogger(this.getClass());
    @Autowired
    private FundBaseOperateService operateService;
    private @Value("#{webProperties['fund.alarm.mobile']}") String mobile;
    /**
     * 线程池 : 考虑到目前接入的基金公司不会很多的情况下使用此Executors,否则使用ThreadPoolExecutor(避免任务多时导致oom)
     */
    private ExecutorService executor = Executors.newFixedThreadPool(3);
    /**
     * 重试的次数
     */
    private static int tryCount = 0;
    /**
     * 当console发起,cps挂了的时候
     */
    private static int countToCountinue = 1;


    @Override
    protected void doJobInternal() {
        executJob();
    }

    /**
     * 执行Job
     */

    private void executJob(){
        try {
            logger.info("基金转理财Fund2GopaySettleJob开始执行");
            //获取所有进行中的02018订单
            List<FundGopayCpsGenOrder> fund02018Processing = operateService.query02018FundOrderProcessing();
            logger.info("基金转理财Fund2GopaySettleJob开始执行,获取获取所有进行中的02018订单[{}]",FastJsonUtil.toJSONString(fund02018Processing));
            if (fund02018Processing != null && fund02018Processing.size() > 0) {
                for (FundGopayCpsGenOrder order : fund02018Processing) {
                    //对单 : 基金订单量少的情况才使用此方法对单,大量订单需要分页;现在的业务量不需要
                    //此计数为防止CONSOLE发往cps中断后进行的校验,防止console与corn同步发起去付款导致不必要的并发问题
                    String settleDay = DateUtils.format(order.getStlmDate(), "yyyyMMdd");

                    List<FundGopayCpsGenOrder> orderList = operateService.checkRedeemOrder(settleDay,order.getGopayOrderId());
                    if(orderList!=null && orderList.size()>0){
                       countToCountinue = countToCountinue*3;
                        processingOrder(orderList,order.getGopayOrderId());
                        logger.info("基金转理财CONSOLE发往CPS中断,重新发起连接=========="+order.getGopayOrderId());
                        continue;
                    }
                    if(countToCountinue % 3 == 0){
                        countToCountinue = 1;
                        continue;
                    }
                    logger.info("基金转理财Fund2GopaySettleJob开始执行,匹配所有赎回订单进行状态更新,02018订单号为[{}]",order.getGopayOrderId());
                    if (operateService.queryAllRedeemOrder(order.getGopayOrderId(),FundSettleStatusEnum.SUCCESS_SETTLE.getValue())) {
                        operateService.update02018OrderSuccess(order.getGopayOrderId());
                        logger.info("基金赎回订单更新为成功-----02018:"+order.getGopayOrderId());
                        continue;
                    }
                    if(operateService.queryAllRedeemOrder(order.getGopayOrderId(),FundSettleStatusEnum.PAY_FAILURE.getValue())){
                        operateService.update02018OrderFail(order.getGopayOrderId());
                        logger.info("基金赎回订单更新为失败-----02018:"+order.getGopayOrderId());
                        continue;
                    }
                }

                //判断当日是否有进行中并且是处于特殊状态的内转订单
                if(!operateService.queryExistInnerTransferRedeemOrder()){
                    logger.error("未存在处于结算异常的02015,02014赎回订单-----"+System.currentTimeMillis());
                    return;
                }
                logger.info("基金转国付宝去付款---存在处于结算异常的02015,02014赎回订单 ==========开始发起任务");
                if(tryCount >=0 && tryCount < 4){
                    sendSMSAlarm("基金转国付宝异常订单处理异常,程序处理受阻,请排查(来源cron警报,class:Fund2GopaySettleJob,请查看cps日志)");
                }
                if(15 < tryCount){
                    logger.error("基金转国付宝去付款---内转业务多次去付款未成功,请查找订单或日志存在的问题,线程关闭 ");
                    return;
                }
                CountDownLatch latch = new CountDownLatch(fund02018Processing.size());
                for (FundGopayCpsGenOrder order : fund02018Processing) {
                    List<FundGopayCpsGenOrder> fund02014NoRes = operateService.queryFundOrderProcessing(order.getGopayOrderId(),
                            IntTxnCd._02014.value.toString(),
                            FundSettleStatusEnum.PAY_LOADING.getValue(), FundSettleStatusEnum.PAY_LOADING_NO_RESULT.getValue());
                    List<FundGopayCpsGenOrder> fund02014CheckError = operateService.queryFundOrderProcessing(order.getGopayOrderId(),
                            IntTxnCd._02014.value.toString(),
                            FundSettleStatusEnum.PAY_LOADING.getValue(),
                            FundSettleStatusEnum.PAY_LOADING_CHECK_ERROR.getValue());
                    List<FundGopayCpsGenOrder> fund02015CheckError = operateService.queryFundOrderProcessing(order.getGopayOrderId(),
                            IntTxnCd._02015.value.toString(),
                            FundSettleStatusEnum.PAY_LOADING.getValue(),
                            FundSettleStatusEnum.PAY_LOADING_CHECK_ERROR.getValue());
                    List<FundGopayCpsGenOrder> fund02015NoRes = operateService.queryFundOrderProcessing(order.getGopayOrderId(),
                            IntTxnCd._02015.value.toString(),
                            FundSettleStatusEnum.PAY_LOADING.getValue(), FundSettleStatusEnum.PAY_LOADING_NO_RESULT.getValue());
                    Tran02014VO tran02014VO = callCpsPayToCustAcct(fund02014CheckError, fund02014NoRes,order.getGopayOrderId());
                    Tran02015VO tran02015VO = callCpsRefundToCustAcct(fund02015CheckError, fund02015NoRes,order.getGopayOrderId());
                    //执行
                    executor.execute(new Fund2GopayTask(latch,tran02014VO,tran02015VO));
                    logger.info("异常订单处理[基金转理财订单号:"+order.getGopayOrderId()
                            +"----tran02014VO="+printVO(tran02014VO)
                            +"----tran02015VO="+printVO(tran02015VO)+"]");
                }
                //如果02014和02015订单50笔,每家基金公司处理时间最长10s,目前业务量,最大60够用
                if(!latch.await(1, TimeUnit.MINUTES)){
                    logger.error("同步锁存器等待超时了 latch.count=" + latch.getCount());
                    executor.awaitTermination(1, TimeUnit.SECONDS);
                    sendSMSAlarm("基金转国付宝异常订单处理异常,同步锁存器等待超时了,线程池关闭,任务将不再执行");
                }
                tryCount ++ ;
                logger.info("基金转理财因订单异常,重新发起次数为:{"+tryCount+"}");
                logger.info("基金转国付宝去付款---存在处于结算异常的02015,02014赎回订单 ==========任务结束");
            }
        } catch (Exception e) {
            logger.error("基金转国付宝去付款---内转业务异常:"+e.getMessage(),e);
        }
        return;
    }

    private void processingOrder(List<FundGopayCpsGenOrder> orderList,String orderId) throws InterruptedException {
        List<FundGopayCpsGenOrder> fund02014NoRes = new ArrayList<>();
        List<FundGopayCpsGenOrder> fund02015NoRes = new ArrayList<>();
        for (FundGopayCpsGenOrder cpsGenOrder : orderList){
            if(IntTxnCd._02015.value.equals(cpsGenOrder.getGopayIntTxnCd())){
                fund02015NoRes.add(cpsGenOrder);
            }else if(IntTxnCd._02014.value.equals(cpsGenOrder.getGopayIntTxnCd())){
                fund02014NoRes.add(cpsGenOrder);
            }
        }
        Tran02014VO tran02014VO = callCpsPayToCustAcct(fund02014NoRes, null,orderId);
        Tran02015VO tran02015VO = callCpsRefundToCustAcct(fund02014NoRes, null,orderId);
        Fund2GopayTask fund2GopayTask = new Fund2GopayTask(null, tran02014VO, tran02015VO);
        executor.execute(fund2GopayTask);
    }


    /**
     * 封装内转-退款业务订单
     * @param args
     * @return
     */
    private Tran02015VO callCpsRefundToCustAcct(Object... args) {
        Tran02015Detail vo = null;
        Tran02015VO tran02015VO = null;
        List<Tran02015Detail> voList = new ArrayList<Tran02015Detail>();
        List<FundGopayCpsGenOrder> mainOrders02015  = args[0] == null ? null : (List<FundGopayCpsGenOrder>)args[0];
        if(mainOrders02015!=null && mainOrders02015.size()>0) {
            for (FundGopayCpsGenOrder order : mainOrders02015) {
                vo = new Tran02015Detail();
                vo.setGopayOrderId(order.getGopayOrderId());
                vo.setMerTxnAmt(order.getTotalOrderAmt());
                vo.setCheckRepeat(false);
                voList.add(vo);
            }
        }
        List<FundGopayCpsGenOrder> mainOrders02015res = args[1] == null ? null : (List<FundGopayCpsGenOrder>)args[1];
        if(mainOrders02015res!=null && mainOrders02015res.size()>0) {
            for (FundGopayCpsGenOrder order : mainOrders02015res) {
                vo = new Tran02015Detail();
                vo.setGopayOrderId(order.getGopayOrderId());
                vo.setMerTxnAmt(order.getTotalOrderAmt());
                vo.setCheckRepeat(true);
                voList.add(vo);
            }
        }
        if(voList!=null && voList.size()>0) {
            tran02015VO = new Tran02015VO();
            tran02015VO.setMainGopayOrderId(args[2] == null ? "" : args[2].toString());
            tran02015VO.setVoList(voList);
        }
        return tran02015VO;
    }

    /**
     * 封装内转-转出业务订单
     * @param args
     * @return
     */
    private Tran02014VO callCpsPayToCustAcct(Object... args) {
        Tran02014VO tran02014VO = null;
        Tran02014Detail vo = null;
        List<Tran02014Detail> voList = new ArrayList<Tran02014Detail>();
        List<FundGopayCpsGenOrder> mainOrders02014 = args[0] == null ? null : (List<FundGopayCpsGenOrder>)args[0];
        if(mainOrders02014 != null && mainOrders02014.size() > 0) {
            for (FundGopayCpsGenOrder order : mainOrders02014) {
                vo = new Tran02014Detail();
                vo.setGopayOrderId(order.getGopayOrderId());
                vo.setMerTxnAmt(order.getTotalOrderAmt());
                vo.setCheckRepeat(false);
                voList.add(vo);
            }
        }
        List<FundGopayCpsGenOrder> mainOrders02014res = args[1] == null ? null : (List<FundGopayCpsGenOrder>)args[1];
        if(mainOrders02014res != null && mainOrders02014res.size()>0) {
            for (FundGopayCpsGenOrder order : mainOrders02014res) {
                vo = new Tran02014Detail();
                vo.setGopayOrderId(order.getGopayOrderId());
                vo.setMerTxnAmt(order.getTotalOrderAmt());
                vo.setCheckRepeat(true);
                voList.add(vo);
            }
        }
        if(voList!=null && voList.size()>0) {
            tran02014VO = new Tran02014VO();
            tran02014VO.setMainGopayOrderId(args[2] == null ? "" : args[2].toString());
            tran02014VO.setVoList(voList);
        }
        return tran02014VO;
    }

    private void sendSMSAlarm(String content){
        try {
            RequestVO req = new RequestVO();
            req.setAppId(SMSAppId._90000.value);
            req.setMobile(mobile);
            req.setContent(content);
            GopaySMSHttpClient.getInstance().createVerificationCode(req);
        } catch (Exception e) {
            logger.error("发送短信通知时异常", e);
        }
    }

    private String printVO(Object obj){
        String ex = "";
        try {
           return JsonUtils.toJson(obj);
        } catch (IOException e) {
            ex = e.getMessage();
        }
        return ex;
    }
}

class Fund2GopayTask implements Runnable {
    private Logger logger = LoggerFactory.getLogger(this.getClass());
    private Tran02015VO target15;
    private Tran02014VO target14;
    private CountDownLatch latch;

    public Fund2GopayTask(CountDownLatch latch,Object... target) {
        this.target14 = target[0] == null ? null : (Tran02014VO) target[0];
        this.target15 = target[1] == null ? null : (Tran02015VO) target[1];
        this.latch = latch;
    }

    @Override
    public void run() {
        try {
            logger.info("基金转国付宝去付款---内转业务(转出)---定时任务调度cps开始"+ FastJsonUtil.toJSONString(target14));
            CPSResponse cpsResponse02014 = null;
            if(target14 != null && target14.getVoList().size()>0) {
                cpsResponse02014 = CallCPSUtil.callByVOTicket(target14);
            }
            logger.info("基金转国付宝去付款---内转业务(转出)---定时任务调度cps结束"+FastJsonUtil.toJSONString(cpsResponse02014) );
        } catch (GopayException e) {
            logger.error("基金转国付宝去付款---内转业务(转出)---定时任务调度cps异常",e);
        }
        try {
            logger.info("基金转国付宝去付款---内转业务(退款)---定时任务调度cps开始"+FastJsonUtil.toJSONString(target15) );
            CPSResponse cpsResponse02015 = null;
            if(target15 != null && target15.getVoList().size()>0) {
                cpsResponse02015 = CallCPSUtil.callByVOTicket(target15);
            }
            logger.info("基金转国付宝去付款---内转业务(退款)---定时任务调度cps结束"+FastJsonUtil.toJSONString(cpsResponse02015) );
        } catch (GopayException e) {
            logger.error("基金转国付宝去付款---内转业务(退款)---定时任务调度cps异常",e);
        }
        if(latch!=null) {
            latch.countDown();
        }
    }
}
